﻿#pragma once

#include <memory>
#include "windows.ui.xaml.media.dxinterop.h"
#include "Helper.h"
#include "primitives.h"
#include "DPI.h"

namespace DXBase
{
	ref class DXAdapter;
	ref class DXContext;
	interface class IRenderTarget;
	namespace D3D
	{
		ref class Texture2D;
	}

	DirectX::XMFLOAT4X4 GetRotationMatrix(Windows::Graphics::Display::DisplayOrientations orientation);
	DXGI_MODE_ROTATION GetRotationMode(Windows::Graphics::Display::DisplayOrientations orientation);

	// this class create some private data structure tied to the context and this interface let
	// create or destroy these resource explicitly, as well as setting the context
    [Windows::Foundation::Metadata::WebHostHidden]
	public interface class IContextBound
	{
		// the context to which this object is attached
		property DXContext^ Context;
		// create the context data now, if needed. throw an error if there is no context.
		void LoadDXData();
		// delete the context data
		void ClearDXData();
	};

	// this should be cached by the render target, otherwise it will be recreated for each render...
    [Windows::Foundation::Metadata::WebHostHidden]
	public ref class NativeRenderTarget sealed
	{
	internal:
		// provided by the IRenderTarget
		Microsoft::WRL::ComPtr<ID3D11Texture2D> m_renderTarget;
		NativeRenderTarget(Microsoft::WRL::ComPtr<ID3D11Texture2D> target) : m_renderTarget(target) {}

		// this is used / set by the context
		bool initialized;
		void Initialize(DXContext^ ctxt, IRenderTarget^ target);
		// do not save context and target (no circular dependencies please)

		Microsoft::WRL::ComPtr<ID3D11Texture2D> m_depthStencil;
		Microsoft::WRL::ComPtr<ID3D11RenderTargetView> m_renderTargetView;
		Microsoft::WRL::ComPtr<ID3D11DepthStencilView> m_depthStencilView;
		Microsoft::WRL::ComPtr<ID2D1Bitmap1> d2dtarget;
		USize uSize;
	};

    [Windows::Foundation::Metadata::WebHostHidden]
	public interface class IRenderTarget : IContextBound
	{
		property Windows::Graphics::Display::DisplayOrientations Orientation { Windows::Graphics::Display::DisplayOrientations get(); }
		property float Dpi 
		{
			float get(); 
			void set(float value);
		}
		property USize PixelSize { USize get(); }
		property USize ViewportOffset { USize get(); }

		NativeRenderTarget^ GetNativeTarget();
		void EndRender();

		D3D::Texture2D^ Snapshot();
	};

	public enum class ContextStatus
	{
		NoDevice,
		CreatingDevice,
		CreatingTargetResources,
		Ready,
	};

    [Windows::Foundation::Metadata::WebHostHidden]
	public delegate void ContextStatusChangedEventHandler(DXContext^ source, ContextStatus status);

	// TODO
	// - Fix: Snapshot() (for ImageSource)
	// - setup viewport
	// - VirtualSurfaceImageSource support http://msdn.microsoft.com/library/windows/apps/hh825871.aspx

	/**
	* This class represent the DirectX context, i.e. target and device. It is used by all other DirectX class to 
	* render themselves
	*/
    [Windows::Foundation::Metadata::WebHostHidden]
    [Windows::UI::Xaml::Data::Bindable] // in c++, adding this attribute to ref classes enables data binding for more info search for 'Bindable' on the page http://go.microsoft.com/fwlink/?LinkId=254639 
	public ref class DXContext sealed : Windows::UI::Xaml::Data::INotifyPropertyChanged
    {
		void Init();
    public:
        DXContext();
		DXContext(DXAdapter^ ada);

	internal: 
		void CreateDeviceResources();
		void UpdateNativeTarget();	
		// check the device is valid, recreate it if not...
		bool ValidateDevice();

	public:
		// get/set the first Viewport, in PixelSize
		property box3d Viewport 
		{ 
			box3d get();
			void set(box3d); 
		}
		// get/set all Viewports, in PixelSize
		property Windows::Foundation::Collections::IVector<box3d>^ Viewports
		{ 
			Windows::Foundation::Collections::IVector<box3d>^ get();
			void set(Windows::Foundation::Collections::IVector<box3d>^ value); 
		}

		// the hardware device
		property DXAdapter^ Adapter { DXAdapter^ get() { return mAdapter; } }

	private:
		IRenderTarget^ mTarget;
	public:
		property IRenderTarget^ Target
		{
			IRenderTarget^ get() { return mTarget; }
			void set(IRenderTarget^ value);
		}
		property USize PixelSize
		{
			USize get() 
			{
				if (Target)
					return Target->PixelSize;
				return makeUSize(0, 0); 
			}
		}
		property float Dpi
		{
			float get() { return Target ? Target->Dpi : DPI::InchToDIP; }
			void set(float value)
			{
				if (Target)
					Target->Dpi = value;
			}
		}
		property Windows::Foundation::Size Size 
		{
			Windows::Foundation::Size get() 
			{
				if (Target)
					return DPI::GetDIP(Target->Dpi, Target->PixelSize); 
				return Windows::Foundation::Size();
			} 
		}

	private:
		ContextStatus mStatus;
	public:
		property ContextStatus Status
		{
			ContextStatus get() { return mStatus; }
		private:
			void set(ContextStatus value)
			{
				if(value == mStatus)
					return;
				mStatus = value;
				OnPropertyChanged("Status");

				if(value == ContextStatus::NoDevice)
					ContextReset(this, value);
			}
		}
		event ContextStatusChangedEventHandler^ ContextReset;

#include "MACROs_DEF.h"
		PROPERTY_CHANGE_EVENT
#include "MACROs_UNDEF.h"

	public:
		property bool Rendering
		{
			bool get() { return isRendering; }
			void set(bool value)
			{
				if (value == isRendering)
					return;
				if (value)
				{
					isRendering = BeginRender();
				}
				else
				{
					EndRender();
					isRendering = false;
				}
				OnPropertyChanged("Rendering");
			}
		}

		void Clear(Windows::UI::Color bg);
		void Flush();

	private:
		bool BeginRender();
		void EndRender();
		bool isRendering;

#pragma region internal DirectX variables

	internal:
		// device resource
		D3D_FEATURE_LEVEL m_featureLevel;
		Microsoft::WRL::ComPtr<ID3D11Device1> m_d3dDevice;
		Microsoft::WRL::ComPtr<ID3D11DeviceContext1> m_d3dContext;
		Microsoft::WRL::ComPtr<ID2D1Device> m_d2dDevice;
		Microsoft::WRL::ComPtr<ID2D1DeviceContext> m_d2dContext;

		NativeRenderTarget^ nativeTarget;
		USize vpSize;

#pragma endregion


	private:
		DebugObject debug;
		DXAdapter^ mAdapter;
  };

  // ================================ HELPER CLASS =================================

	// many class depends on context and should reload or reset when the context got some status
	// and they should do so only once.
	// this class manage context watching and single data loading when appropriate
	ref class ContextWatcher sealed
	{
	private:
		~ContextWatcher();

		// event
		Platform::WeakReference mWeakTarget; // no circular reference please!
		Windows::Foundation::EventRegistrationToken eventtoken;
		DXContext^ mContext;
		bool mWatching;
		void RegisterEvent();
		void UnregisterEvent();
		void OnContextReset(DXContext^ sender, ContextStatus status);

	public:
		ContextWatcher();
		property DXContext^ Context 
		{
			DXContext^ get();
			void set(DXContext^ value);
		}
		property IContextBound^ Target
		{
			IContextBound^ get();
			void set(IContextBound^ value);
		}

		// obsolete
		property static Windows::Foundation::IAsyncOperation<bool>^ OperationSuccess { Windows::Foundation::IAsyncOperation<bool>^ get() { return finishedSuccess; } }
	private:
		static Windows::Foundation::IAsyncOperation<bool>^ finishedSuccess;
	};
}